package com.canyou.operlog;

import cn.hutool.core.date.DateUtil;
import com.canyou.utils.FormatUtils;
import com.canyou.utils.StringUtils;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import org.joda.money.BigMoneyProvider;
import org.joda.money.Money;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import static org.springframework.beans.BeanUtils.getPropertyDescriptor;
import static org.springframework.beans.BeanUtils.getPropertyDescriptors;

/**
 * 比较属性字段是否相等
 *
 * @author fy
 * @date 20/04/22 10:49
 */
public final class BeanUtils {

    private BeanUtils() {
    }

    private static final String DEFAULT_DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";
    private static final String DEFAULT_TIME_PATTERN = "HH:mm:ss";

    /**
     * 比较出属性字段值不相等的属性
     *
     * @param source            原值
     * @param target            修改后的值
     * @param containProperties 要比较的属性字段名称集合
     * @return
     * @throws BeansException
     */
    public static List<CompareRes> diffProperties(Object source, Object target, @Nullable String... containProperties) throws BeansException {
        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");
        // 修改的字段结果集合
        List<CompareRes> list = new ArrayList<>();
        List<String> containList = (containProperties != null ? Arrays.asList(containProperties) : null);
        if (containList == null || containList.isEmpty()) {
            return list;
        }
        Class<?> actualEditable = source.getClass();
        PropertyDescriptor[] sourcePds = getPropertyDescriptors(actualEditable);

        for (PropertyDescriptor sourcePd : sourcePds) {
            Method sourceReadMethod = sourcePd.getReadMethod();
            String sourcePdName = sourcePd.getName();
            if (sourceReadMethod != null && containList.contains(sourcePdName)) {
                PropertyDescriptor targetPd = getPropertyDescriptor(target.getClass(), sourcePdName);
                if (targetPd != null) {
                    Method targetReadMethod = targetPd.getReadMethod();
                    if (targetReadMethod != null) {
                        if (!ClassUtils.isAssignable(targetReadMethod.getReturnType(), sourceReadMethod.getReturnType())) {
                            throw new FatalBeanException(
                                    "property '" + targetPd.getName() + "' type in source and target not assignable");
                        }
                        // 只比较基础数据类型字段, 复杂的引用类型数据类型无法比较出前后的修改
                        if (!org.springframework.beans.BeanUtils.isSimpleValueType(sourceReadMethod.getReturnType()) &&
                                !ZonedDateTime.class.isAssignableFrom(sourceReadMethod.getReturnType()) &&
                                !LocalDateTime.class.isAssignableFrom(sourceReadMethod.getReturnType()) &&
                                !LocalTime.class.isAssignableFrom(sourceReadMethod.getReturnType()) &&
                                !Date.class.isAssignableFrom(sourceReadMethod.getReturnType()) &&
                                !BigMoneyProvider.class.isAssignableFrom(sourceReadMethod.getReturnType())) {
                            throw new FatalBeanException("property '" + sourcePdName + "' not simpleValueType");
                        }
                        try {
                            if (!Modifier.isPublic(sourceReadMethod.getDeclaringClass().getModifiers())) {
                                sourceReadMethod.setAccessible(true);
                            }
                            Object sourceValue = sourceReadMethod.invoke(source);

                            if (!Modifier.isPublic(targetReadMethod.getDeclaringClass().getModifiers())) {
                                targetReadMethod.setAccessible(true);
                            }
                            Object targetValue = targetReadMethod.invoke(target);
                            boolean notEqual = false;
                            if (sourceValue == null) {
                                if (null != targetValue) {
                                    notEqual = true;
                                }
                            } else if (!sourceValue.equals(targetValue)) {
                                notEqual = true;
                            }
                            if (notEqual) {
                                list.add(new CompareRes()
                                        .setPropertyName(sourcePdName)
                                        .setSourceValue(sourceValue)
                                        .setTargetValue(targetValue));
                            }
                        } catch (Exception ex) {
                            throw new FatalBeanException(
                                    "Could not compare property '" + targetPd.getName() + "' in source with target", ex);
                        }
                    }
                }
            }
        }
        return list;
    }

    /**
     * 比较出属性字段值不相等的属性并变成可读语句
     *
     * @param source            原值
     * @param target            修改后的值
     * @param containProperties 要比较的属性字段名称集合
     * @return
     * @throws BeansException
     */
    public static List<String> compareProperties(Object source, Object target, @Nullable String... containProperties) throws BeansException {
        // 找到前后值不相等的字段
        List<CompareRes> compareResList = diffProperties(source, target, containProperties);
        List<String> list = new ArrayList<>();
        if (compareResList == null || compareResList.isEmpty()) {
            return list;
        }
        for (CompareRes compareRes : compareResList) {
            String propertyName = compareRes.getPropertyName();
            try {
                String readablePropertyName = propertyName;
                ApiModelProperty apiModelProperty = target.getClass().getDeclaredField(propertyName).getAnnotation(ApiModelProperty.class);
                if (apiModelProperty != null && StringUtils.isNotBlank(apiModelProperty.value())) {
                    readablePropertyName = apiModelProperty.value();
                }
                Object sourceValue = compareRes.getSourceValue();
                Object targetValue = compareRes.getTargetValue();

                Object orValue = compareRes.getSourceValue();
                Object toValue = compareRes.getTargetValue();
                // 如果配置了属性映射, 则使用映射值
                DataMaps dataMaps = target.getClass().getDeclaredField(propertyName).getAnnotation(DataMaps.class);
                if (dataMaps != null && dataMaps.value().length > 0) {
                    DataMap[] maps = dataMaps.value();
                    for (DataMap map : maps) {
                        if (map.value().equalsIgnoreCase(String.valueOf(sourceValue))) {
                            orValue = map.name();
                            continue;
                        }
                        if (map.value().equalsIgnoreCase(String.valueOf(targetValue))) {
                            toValue = map.name();
                            continue;
                        }
                    }
                } else {
                    // 如果设置了日期格式化的注解, 使用注解的格式格式化日期
                    DateFormat dateFormat = target.getClass().getDeclaredField(propertyName).getAnnotation(DateFormat.class);
                    String datePattern = DEFAULT_DATE_PATTERN;
                    if (dateFormat != null && StringUtils.isNotBlank(dateFormat.value())) {
                        datePattern = dateFormat.value();
                    }
                    // 格式化显示修改前后的值
                    if (targetValue instanceof Date) {
                        toValue = DateUtil.format((Date) targetValue, datePattern);
                        orValue = DateUtil.format((Date) sourceValue, datePattern);
                    }
                    if (targetValue instanceof ZonedDateTime) {
                        toValue = ((ZonedDateTime) targetValue).format(DateTimeFormatter.ofPattern(datePattern));
                        orValue = ((ZonedDateTime) sourceValue).format(DateTimeFormatter.ofPattern(datePattern));
                    }
                    if (targetValue instanceof LocalDateTime) {
                        toValue = ((LocalDateTime) targetValue).format(DateTimeFormatter.ofPattern(datePattern));
                        orValue = ((LocalDateTime) sourceValue).format(DateTimeFormatter.ofPattern(datePattern));
                    }
                    if (targetValue instanceof LocalTime) {
                        toValue = ((LocalTime) targetValue).format(DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN));
                        orValue = ((LocalTime) sourceValue).format(DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN));
                    }
                    if (targetValue instanceof BigMoneyProvider) {
                        toValue = FormatUtils.fmtMicrometer(((Money) targetValue));
                        orValue = FormatUtils.fmtMicrometer(((Money) sourceValue));
                    }
                }

                list.add(StringUtils.format("{}从【{}】修改为【{}】", readablePropertyName, orValue, toValue));
            } catch (Exception ex) {
                throw new RuntimeException(
                        "error compare property '" + propertyName + "' in source with target", ex);
            }
        }
        return list;
    }

    /**
     * 模型比较属性值 修改结果
     */
    @Data
    @Accessors(chain = true)
    public static class CompareRes {

        /**
         * 属性名称
         */
        private String propertyName;

        /**
         * 修改后的值
         */
        private Object targetValue;

        /**
         * 原值
         */
        private Object sourceValue;
    }

}
